﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.Input;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarAnalysis
{
    [Serializable]
    internal sealed class LexicalAliasTable
    {
        [NonSerialized]
        private ReadOnlyDictionary<string, LexicalAlias> _aliasesWrapper;
        private readonly Dictionary<string, LexicalAlias> _aliases;
        public ReadOnlyDictionary<string, LexicalAlias> Aliases
        {
            get
            {
                if (_aliasesWrapper == null)
                    _aliasesWrapper = _aliases.AsReadOnly();
                return _aliasesWrapper;
            }
        }

        internal LexicalAliasTable()
        {
            _aliases = new Dictionary<string, LexicalAlias>();
        }

        internal LexicalAlias Define(string name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return Define(name, default(SourceContext));
        }

        internal LexicalAlias Define(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return Define(name.Name, name.Context);
        }

        internal LexicalAlias Define(string name, SourceContext context)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias;
            if (_aliases.TryGetValue(name, out alias))
            {
                string message = string.Format(SR.LexicalAliasDefined, name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(alias.Context);
                e.AddLocation(context);
                throw e;
            }
            alias = new LexicalAlias();
            alias.Number = _aliases.Count;
            alias.Name = name;
            alias.Context = context;
            _aliases.Add(name, alias);
            return alias;
        }

        internal LexicalAlias Define(string name, LexicalExpression pattern)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias = Define(name);
            if (pattern != null)
                alias.UpdatePattern(pattern);
            return alias;
        }

        internal LexicalAlias Define(Identifier name, LexicalExpression pattern)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias = Define(name);
            if (pattern != null)
                alias.UpdatePattern(pattern);
            return alias;
        }

        internal LexicalAlias Define(string name, SourceContext context, LexicalExpression pattern)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias = Define(name, context);
            if (pattern != null)
                alias.UpdatePattern(pattern);
            return alias;
        }

        internal LexicalAlias Define(LexicalAliasDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return Define(n.Name, n.Pattern);
        }

        internal void Define(LexicalDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (LexicalDeclarator item in n.Declarators)
                if (item.IsAlias)
                    Define(item.AsAlias.Declarator);
        }

        public LexicalAlias TryLookup(string name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias;
            _aliases.TryGetValue(name, out alias);
            return alias;
        }

        public LexicalAlias TryLookup(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return TryLookup(name.Name);
        }

        public LexicalAlias Lookup(string name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return Lookup(name, null);
        }

        public LexicalAlias Lookup(Identifier name)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            return Lookup(name.Name, name.Context);
        }

        public LexicalAlias Lookup(string name, SourceContext context)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name));
            LexicalAlias alias;
            if (!_aliases.TryGetValue(name, out alias))
            {
                string message = string.Format(SR.LexicalAliasNotDefined, name.AsSingleQuote());
                NameErrorException e = new NameErrorException(message);
                e.AddLocation(context);
                throw e;
            }
            return alias;
        }

        internal void Simplify()
        {
            RecursiveLexicalAliasSimplifier simplifier = new RecursiveLexicalAliasSimplifier(this);
            foreach (var t in _aliases)
                simplifier.TrySimplify(t.Value);
        }

        internal void Check()
        {
            foreach (var t in _aliases)
            {
                RecursiveLexicalAliasChecker checker = new RecursiveLexicalAliasChecker(this, t.Value);
                if (checker.CheckLexicalExpression(t.Value.Pattern))
                {
                    string message = string.Format(SR.LexicalAliasRecursive, t.Key.AsSingleQuote());
                    GrammarErrorException e = new GrammarErrorException(message);
                    e.AddLocation(t.Value.Context);
                    throw e;
                }
            }
        }

        public sealed override string ToString()
        {
            return string.Join("\n", _aliases.Values);
        }
    }
}
